﻿using System;
using System.IO.MemoryMappedFiles;
using System.Threading;
using Pfz.Caching;
using Pfz.Threading;

namespace Pfz.Remoting
{
	/// <summary>
	/// Creates a Listener using MemoryMappedFiles (local-only, but very fast).
	/// </summary>
	public sealed class MmfChannellerListener:
		ThreadSafeDisposable,
		IChannellerListener
	{
		private MemoryMappedFile _file;
		private MemoryMappedViewAccessor _accessor;
		private Mutex _mutex;

		private EventWaitHandle _requestEvent;
		private EventWaitHandle _resultEvent;
		private ManualResetEvent _mreDisposed;
		private WaitHandle[] _waitHandles;

		/// <summary>
		/// Creates a new listener with the given name.
		/// </summary>
		public MmfChannellerListener(string name)
		{
			if (name == null)
				throw new ArgumentNullException("name");

			try
			{
				bool createdNew;
				_mutex = new Mutex(false, "Pfz.Remoting.MmfListener.Mutex:" + name, out createdNew);
				if (!createdNew)
					throw new RemotingException("The given listener name is already used.");

				_requestEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "Pfz.Remoting.MmfListener.RequestEvent:" + name, out createdNew);
				if (!createdNew)
					throw new RemotingException("The given listener name is already used.");

				_resultEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "Pfz.Remoting.MmfListener.ResultEvent:" + name, out createdNew);
				if (!createdNew)
					throw new RemotingException("The given listener name is already used.");

				_file = MemoryMappedFile.CreateNew("Pfz.Remoting.MmfListener.MemoryMappedFile:" + name, sizeof(long));

				_accessor = _file.CreateViewAccessor();

				_mreDisposed = new ManualResetEvent(false);
				_waitHandles= new WaitHandle[2];
				_waitHandles[0] = _mreDisposed;
				_waitHandles[1] = _requestEvent;
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		/// <summary>
		/// Releases all the resources used by this object.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				var mreDisposed = _mreDisposed;
				if (mreDisposed != null)
					mreDisposed.Set();

				Disposer.Dispose(ref _requestEvent);
				Disposer.Dispose(ref _resultEvent);
				Disposer.Dispose(ref _mutex);
				Disposer.Dispose(ref _accessor);
				Disposer.Dispose(ref _file);
			}

			base.Dispose(disposing);
		}

		/// <summary>
		/// Tries to accept a new channeller.
		/// </summary>
		/// <returns></returns>
		public MmfChanneller TryAccept()
		{
			CheckUndisposed();

			int index = WaitHandle.WaitAny(_waitHandles);
			if (index == 0)
			{
				var mreDisposed = _mreDisposed;
				if (mreDisposed != null)
				{
					_mreDisposed = null;
					mreDisposed.Dispose();
				}

				return null;
			}

			long newId = TicksOrIncrement.GetNext();

			lock(DisposeLock)
			{
				if (WasDisposed)
				{
					var mreDisposed = _mreDisposed;
					if (mreDisposed != null)
					{
						_mreDisposed = null;
						mreDisposed.Dispose();
					}

					return null;
				}

				var result = new MmfChanneller(newId, true);
				_accessor.Write(0, newId);
				_resultEvent.Set();
				return result;
			}
		}

		IChanneller IChannellerListener.TryAccept()
		{
			return TryAccept();
		}
	}
}
